// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <suggest/suggest.h>

#include <memory>
#include <fstream>
#include <iostream>

#include "aosp/suggest/core/layout/proximity_info.h"
#include "aosp/suggest/policyimpl/dictionary/dictionary_structure_with_buffer_policy_factory.h"
#include "aosp/suggest/core/dictionary/dictionary.h"
#include "aosp/suggest/core/session/dic_traverse_session.h"
#include "aosp/suggest/core/suggest_options.h"

namespace {
std::vector<int> FillVector(int size, std::function<int (int)> fillFunc) {
   std::vector<int> array(size);
   for(int i=0; i<size; ++i) {
      array[i] = fillFunc(i);
   }
   return array;
}
}

namespace suggest {

using namespace latinime;

Touch::Touch(vec2f pos, charcode code) : pos(pos), code(code) {}


Touch::Touch(vec2f pos, const SuggestEngine& engine) : pos(pos) {
   code = engine.GetKeyAt(pos).code;
}

Touch::Touch(charcode code, const SuggestEngine& engine) : code(code) {
   pos = engine.GetKey(code).rect.center();
}


class SuggestEngineImpl;

class SuggestSessionImpl : public SuggestSession {
 public:
   SuggestSessionImpl(SuggestEngineImpl* engine);
   virtual const std::list<Suggestion>& GetSuggestions(
         const std::vector<Touch> &touches,
         std::string previous_word);

  private:
   std::unique_ptr<SuggestOptions> options_;
   std::unique_ptr<DicTraverseSession> session_;
   std::list<Suggestion> suggestions_;
   SuggestEngineImpl* engine_;
};

class SuggestEngineImpl : public SuggestEngine {
  public:
   SuggestEngineImpl(vec2f keyboard_size, vec2f common_key_size,
                   const std::vector<Key> &keys,
                   const SuggestParameters &p) : parameters_(p), keys_(keys) {
      JNIEnv fakeEnv;
      int num_keys = keys.size();

      // build proximity chars grid
      std::vector<int> proximityChars(p.grid_cells.x * p.grid_cells.y * MAX_PROXIMITY_CHARS_SIZE, 0);
      vec2f cell_size = div_elem(keyboard_size, p.grid_cells);
      vec2f search_box_size = mul(common_key_size, p.search_box_size_factor);

      for (int y=0; y<p.grid_cells.y; ++y) {
         for (int x=0; x<p.grid_cells.x; ++x) {
            rect2f cell(mul_elem(vec2f(x, y), cell_size), cell_size);
            rect2f search_rect = cell.resized(search_box_size);

            int i = ((y * p.grid_cells.x) + x) * MAX_PROXIMITY_CHARS_SIZE;

            for (int k=0; k<num_keys; ++k) {
               if (search_rect.intersects(keys[k].rect)) {
                  proximityChars[i++] = keys[k].code;
               }
            }
         }
       }
#ifdef FLAG_DEBUG
      for (int y=0; y<p.grid_cells.y; ++y) {
         for (int x=0; x<p.grid_cells.x; ++x) {
               AKLOGI("(%d, %d) = %c %c %c %c %c %c %c %c", x, y,
                      (char)proximityChars[((y * p.grid_cells.x) + x) * MAX_PROXIMITY_CHARS_SIZE + 0],
                      (char)proximityChars[((y * p.grid_cells.x) + x) * MAX_PROXIMITY_CHARS_SIZE + 1],
                      (char)proximityChars[((y * p.grid_cells.x) + x) * MAX_PROXIMITY_CHARS_SIZE + 2],
                      (char)proximityChars[((y * p.grid_cells.x) + x) * MAX_PROXIMITY_CHARS_SIZE + 3],
                      (char)proximityChars[((y * p.grid_cells.x) + x) * MAX_PROXIMITY_CHARS_SIZE + 4],
                      (char)proximityChars[((y * p.grid_cells.x) + x) * MAX_PROXIMITY_CHARS_SIZE + 5],
                      (char)proximityChars[((y * p.grid_cells.x) + x) * MAX_PROXIMITY_CHARS_SIZE + 6],
                      (char)proximityChars[((y * p.grid_cells.x) + x) * MAX_PROXIMITY_CHARS_SIZE + 7]);
           }
       }
#endif
       // build aosp compabtile data structures
      std::vector<int> keyXCoordinates = FillVector(num_keys, [&](int i) -> int { return keys[i].rect.pos.x; });
      std::vector<int> keyYCoordinates = FillVector(num_keys, [&](int i) -> int { return keys[i].rect.pos.y; });
      std::vector<int> keyWidths = FillVector(num_keys, [&](int i) -> int { return keys[i].rect.size.x; });
      std::vector<int> keyHeights = FillVector(num_keys, [&](int i) -> int { return keys[i].rect.size.y; });
      std::vector<int> keyCharCodes = FillVector(num_keys, [&](int i) -> int { return keys[i].code; });

#ifdef DEBUG
      for (int y=0; y<grid_cells.y; ++y) {
         for (int x=0; x<grid_cells.x; ++x) {
               AKLOGI("(%d, %d) = %c", keyXCoordinates[y * 5 + x], keyYCoordinates[y * 5 + x], (char)keyCharCodes[y * 5 + x]);
           }
       }
#endif

       // initialize aosp dictionary and proximity info
      proximity_info_.reset(new ProximityInfo(&fakeEnv, p.locale.c_str(),
            keyboard_size.x, keyboard_size.y,
            p.grid_cells.x, p.grid_cells.y,
            common_key_size.x, common_key_size.y,
            &proximityChars,
            num_keys,
            &keyXCoordinates, &keyYCoordinates,
            &keyWidths, &keyHeights,
            &keyCharCodes,
            NULL, NULL, NULL));
   }

   virtual bool LoadDictionary(std::string locale) {
      JNIEnv fakeEnv;

      std::string dict_filename = "/usr/share/libsuggest/" + locale + ".dict";
      std::cout << "Loading: " << dict_filename << std::endl;
      std::ifstream stream(dict_filename);
      if (!stream.good())
         return false;
      stream.seekg(0, stream.end);
      int filesize = stream.tellg();
      stream.close();
      std::cout << "Size: " << filesize << std::endl;

      dict_structure_ =
            DictionaryStructureWithBufferPolicyFactory::newDictionaryStructureWithBufferPolicy(
               dict_filename.c_str(), 0, filesize, false);

      dictionary_.reset(new Dictionary(&fakeEnv, dict_structure_));
      return true;
   }

   virtual Key GetKeyAt(vec2f pos) const {
      for(const Key &key: keys_) {
         if (key.rect.contains(pos)) {
            return key;
         }
      }
      return Key::InvalidKey;
   }

   virtual Key GetKey(charcode code) const {
      for(const Key &key: keys_) {
         if (key.code == code) {
            return key;
         }
      }
      return Key::InvalidKey;
   }

   virtual SuggestSession* NewSession() {
      return new SuggestSessionImpl(this);
   }

public:
   ProximityInfo* proximity_info(){ return proximity_info_.get(); }
   Dictionary* dictionary(){ return dictionary_.get(); }
   const SuggestParameters& parameters() { return parameters_; }
  private:
   std::unique_ptr<ProximityInfo> proximity_info_;
   DictionaryStructureWithBufferPolicy *dict_structure_;
   std::unique_ptr<Dictionary> dictionary_;
   SuggestParameters parameters_;
   std::vector<Key> keys_;

};

SuggestSessionImpl::SuggestSessionImpl(SuggestEngineImpl* engine) : engine_(engine) {
   JNIEnv fakeEnv;

   options_.reset(new SuggestOptions(NULL, 0));
   session_.reset(new DicTraverseSession(&fakeEnv,
         engine->parameters().locale.c_str(), true));

}

const std::list<Suggestion>& SuggestSessionImpl::GetSuggestions(
         const std::vector<Touch> &touches,
         std::string previous_word="") {
   suggestions_.clear();

   std::vector<int> x_coords = FillVector(touches.size(),
         [&](int i) { return touches[i].pos.x; });
   std::vector<int> y_coords = FillVector(touches.size(),
         [&](int i) { return touches[i].pos.y; });
   std::vector<int> codes = FillVector(touches.size(),
         [&](int i) { return touches[i].code; });
   std::vector<int> times = FillVector(touches.size(),
         [&](int i) { return i; });
   std::vector<int> pointer_ids(touches.size(), 0);


   int outWords[1024] = {0};
   int frequencies[1024] = {0};
   int spaceIndices[1024] = {0};
   int outputTypes[1024] = {0};
   int outputCommitFirstWordConfidence[1024] = {0};

   engine_->dictionary()->getSuggestions(
      engine_->proximity_info(), session_.get(),
      &x_coords[0], &y_coords[0], &times[0], &pointer_ids[0], &codes[0],
      touches.size(),
      NULL, 0,
      0,
      options_.get(),
      outWords, frequencies, spaceIndices, outputTypes,
      outputCommitFirstWordConfidence);

   for (int i = 0; i < MAX_RESULTS; ++i) {
      Suggestion suggestion;

      for (int j=0; j<MAX_WORD_LENGTH; ++j) {
         char code = outWords[i * MAX_WORD_LENGTH + j];
         if (code == 0)
            break;
         suggestion.word.append(1, (char)outWords[i * MAX_WORD_LENGTH + j]);
      }

      if (suggestion.word.size() > 0) {
         suggestion.frequency = frequencies[i];
         suggestions_.push_back(suggestion);
      } else {
         break;
      }
   }

   return suggestions_;
}

Key Key::InvalidKey(vec2f(0, 0), vec2f(0, 0), 0);

SuggestParameters::SuggestParameters(std::string locale)
   : grid_cells(10, 10),
     search_box_size_factor(1.5),
     locale(locale) {}

SuggestEngine* NewSuggestEngine(vec2f keyboard_size, vec2f common_key_size,
                          const std::vector<Key> &keylist,
                          const SuggestParameters &parameters) {
   return new SuggestEngineImpl(keyboard_size, common_key_size,
                         keylist, parameters);
}

}  // namespace suggest